﻿using System;
using System.Collections.Generic;
using UnityEngine;

namespace Mars.MeshUI
{
    public class VertexHelperEx : IDisposable
    {
        public enum VertexIndicType
        {
            VertexQuad,
            VertexQuad6H,
            VertexQuad6V,
            Max,
        }

        public class SegmentInfo
        {
            public bool visible;
            public MeshCanvasDirtyFlag dirtyFlag;
            public VertexIndicType indicType;

            public int startIdx;
            public int indexIndic;
            public int length;

            public VertexData[] vertexData;
        }

        struct MeshVertexOffsetInfo
        {
            public bool useColor;
            public Mesh mesh;
        }

        private List<List<ushort>> m_indicRaw = null;
        private int useVertexLength = 0;
        private int useIndicsLength = 0;
        private List<SegmentInfo> m_SegmentInfos = ListPool<SegmentInfo>.Get();
        private List<ushort> m_SegmentIndices = ListPool<ushort>.Get();
        private List<ushort> m_indicLength = ListPool<ushort>.Get();
        private MeshVertexOffsetInfo m_offsetInfo = new MeshVertexOffsetInfo();
        private MeshCanvasDirtyFlag m_dirtyFlag = MeshCanvasDirtyFlag.kNone;

        private List<Vector3> m_Positions = null;
        private List<Color32> m_Colors = null;
        private List<Vector4> m_Uv0S = null;
        private List<int> m_Indices = null;

        private bool isNonNativeInit = false;

        public VertexHelperEx(Mesh m, int count, bool useColor)
        {
            InitSegment(count);
            InitIndic();
            ResetMeshInfo(m, useColor);

            InitNonNativeList();
        }


        public void InitNonNativeList()
        {
            if (isNonNativeInit || m_offsetInfo.mesh == null)
                return;

            m_Positions = ListPool<Vector3>.Get();
            m_Uv0S = ListPool<Vector4>.Get();
            if (m_offsetInfo.useColor)
            {
                m_Colors = ListPool<Color32>.Get();
            }

            m_Indices = ListPool<int>.Get();
            ResetListCapacity(m_offsetInfo.mesh.vertexCount, (int)m_offsetInfo.mesh.GetIndexCount(0));
            isNonNativeInit = true;
        }

        public void ClearNonNativeList()
        {
            ListPool<Vector3>.Release(m_Positions);
            ListPool<Vector4>.Release(m_Uv0S);
            if (m_offsetInfo.useColor)
            {
                ListPool<Color32>.Release(m_Colors);
            }

            ListPool<int>.Release(m_Indices);
        }

        public void ResetListCapacity(int vertexCapactiy, int indicCapacity)
        {
            if (m_Positions.Capacity < vertexCapactiy)
            {
                m_Positions.Capacity = vertexCapactiy;
                m_Uv0S.Capacity = vertexCapactiy;

                if (m_offsetInfo.useColor)
                {
                    m_Colors.Capacity = vertexCapactiy;
                }
            }

            if (m_Indices.Capacity < indicCapacity)
            {
                m_Indices.Capacity = indicCapacity;
            }
        }

        public void Dispose()
        {
            ListPool<SegmentInfo>.Release(m_SegmentInfos);
            ListPool<ushort>.Release(m_SegmentIndices);
            ListPool<ushort>.Release(m_indicLength);

            for (int i = 0; i < m_indicRaw.Count; i++)
            {
                ListPool<ushort>.Release(m_indicRaw[i]);
            }

            ClearNonNativeList();

            m_indicRaw = null;
            m_SegmentInfos = null;
            m_SegmentIndices = null;
            m_indicLength = null;
        }

        public void ResetMeshInfo(Mesh mesh, bool useColor)
        {
            if (mesh == null || m_offsetInfo.mesh == mesh)
                return;

            m_offsetInfo.mesh = mesh;
            m_offsetInfo.useColor = useColor;
        }

        public void InitIndic()
        {
            m_indicRaw = new List<List<ushort>>((int)VertexIndicType.Max);
            for (int i = 0; i < (int)VertexIndicType.Max; i++)
            {
                m_indicRaw.Add(ListPool<ushort>.Get());
            }

            var quad = m_indicRaw[(int)VertexIndicType.VertexQuad];
            AddTri(0, 1, 2, quad);
            AddTri(2, 3, 0, quad);
            m_indicLength.Add(4);

            var quadH = m_indicRaw[(int)VertexIndicType.VertexQuad6H];
            AddTri(0, 1, 2, quadH);
            AddTri(2, 3, 0, quadH);
            AddTri(4, 5, 2, quadH);
            AddTri(2, 3, 4, quadH);
            m_indicLength.Add(6);

            var quadV = m_indicRaw[(int)VertexIndicType.VertexQuad6V];
            AddTri(0, 1, 2, quadV);
            AddTri(2, 3, 0, quadV);
            AddTri(1, 4, 5, quadV);
            AddTri(5, 2, 1, quadV);
            m_indicLength.Add(6);
        }

        void AddTri(ushort v0, ushort v1, ushort v2, List<ushort> vs)
        {
            vs.Add(v0);
            vs.Add(v1);
            vs.Add(v2);
        }

        public void InitSegment(int count)
        {
            m_SegmentInfos.Capacity = count;

            for (int i = 0; i < count; i++)
            {
                m_SegmentInfos.Add(new SegmentInfo());
            }
        }

        public void SetVertexDataById(VertexData[] vertexDatas, VertexIndicType type, int id)
        {
            if (vertexDatas == null)
            {
                return;
            }

            SegmentInfo segment = m_SegmentInfos[id];

            if (segment.length != vertexDatas.Length)
            {
                if ((int)type >= (int)VertexIndicType.Max)
                {
                    type = VertexIndicType.VertexQuad;
                }

                segment.length = vertexDatas.Length;
                segment.startIdx = useVertexLength;
                segment.vertexData = vertexDatas;
                segment.indicType = type;

                m_SegmentInfos[id] = segment;
                useVertexLength += segment.length;

                m_dirtyFlag |= MeshCanvasDirtyFlag.kVisible;
            }
        }

        public void SetVertexLenghtDirtyById(int id, int length)
        {
            SegmentInfo segment = m_SegmentInfos[id];

            if (segment.length != length)
            {
                m_SegmentInfos[id].length = length;

                m_dirtyFlag |= MeshCanvasDirtyFlag.kVisible;
            }
        }

        public void SetDirtyById(int id, MeshCanvasDirtyFlag flag)
        {
            if ((flag & MeshCanvasDirtyFlag.kAll) == 0)
                return;

            m_SegmentInfos[id].dirtyFlag |= flag;
            m_dirtyFlag |= flag;
        }

        public void SetVisibleDirtyById(int id, bool visible)
        {
            if (m_SegmentInfos[id].visible != visible)
            {
                m_SegmentInfos[id].visible = visible;
                m_dirtyFlag |= MeshCanvasDirtyFlag.kVisible;
            }
        }

        public void ResetSegmentRange()
        {
            useVertexLength = 0;
            useIndicsLength = 0;
            for (int i = 0; i < m_SegmentInfos.Count; i++)
            {
                var segment = m_SegmentInfos[i];
                if (!segment.visible)
                {
                    continue;
                }

                segment.startIdx = useVertexLength;
                segment.indexIndic = useIndicsLength;

                useVertexLength += segment.length;
                useIndicsLength += m_indicRaw[(int)segment.indicType].Count * segment.length /
                                   m_indicLength[(int)segment.indicType];

                segment.dirtyFlag |= MeshCanvasDirtyFlag.kAll | MeshCanvasDirtyFlag.kVisible;
            }
        }

        public void FillMeshById(Mesh mesh, bool modified = true)
        {
            if (useVertexLength >= 65000)
            {
                throw new ArgumentException("Mesh can not have more than 65000 vertices");
            }

            if (m_dirtyFlag == 0)
            {
                return;
            }

            if (!isNonNativeInit)
            {
                return;
            }

            if ((m_dirtyFlag & MeshCanvasDirtyFlag.kVisible) == MeshCanvasDirtyFlag.kVisible)
            {
                ResetSegmentRange();

                ResetListCapacity(useVertexLength, useIndicsLength);

                m_Positions.Clear();
                m_Indices.Clear();
                m_Uv0S.Clear();
                if (m_offsetInfo.useColor)
                {
                    m_Colors.Clear();
                }
            }


            for (int i = 0; i < m_SegmentInfos.Count; i++)
            {
                var segment = m_SegmentInfos[i];
                if (!segment.visible)
                {
                    continue;
                }


                if ((segment.dirtyFlag & MeshCanvasDirtyFlag.kVisible) == MeshCanvasDirtyFlag.kVisible)
                {
                    var triLength = m_indicLength[(int)segment.indicType];
                    var triCount = segment.length / triLength;
                    var segmentindic = m_indicRaw[(int)segment.indicType];
                    var IndicStartIdx = segment.indexIndic;
                    var IndicIdx = 0;

                    var offsetIndex = 0;

                    for (int triIdx = 0; triIdx < triCount; triIdx++)
                    {
                        offsetIndex = segment.startIdx + triIdx * triLength;
                        for (int indicRawIdx = 0; indicRawIdx < segmentindic.Count; indicRawIdx++, IndicIdx++)
                        {
                            m_Indices.Add((ushort)(segmentindic[indicRawIdx] + offsetIndex));
                        }
                    }

                    for (int j = 0; j < segment.length; j++)
                    {
                        m_Positions.Add(segment.vertexData[j].position);
                        m_Uv0S.Add(segment.vertexData[j].uv);
                    }

                    if (m_offsetInfo.useColor)
                    {
                        for (int j = 0; j < segment.length; j++)
                        {
                            m_Colors.Add(segment.vertexData[j].color);
                        }
                    }

                    segment.dirtyFlag = MeshCanvasDirtyFlag.kNone;
                    continue;
                }

                if ((segment.dirtyFlag & MeshCanvasDirtyFlag.kPosition) == MeshCanvasDirtyFlag.kPosition)
                {
                    for (int j = 0; j < segment.length; j++)
                    {
                        m_Positions[segment.startIdx + j] = segment.vertexData[j].position;
                    }
                }

                if (m_offsetInfo.useColor)
                {
                    if ((segment.dirtyFlag & MeshCanvasDirtyFlag.kColor) == MeshCanvasDirtyFlag.kColor)
                    {
                        for (int j = 0; j < segment.length; j++)
                        {
                            m_Colors[segment.startIdx + j] = segment.vertexData[j].color;
                        }
                    }
                }

                if ((segment.dirtyFlag & MeshCanvasDirtyFlag.kUv) == MeshCanvasDirtyFlag.kUv)
                {
                    for (int j = 0; j < segment.length; j++)
                    {
                        m_Uv0S[segment.startIdx + j] = segment.vertexData[j].uv;
                    }
                }

                segment.dirtyFlag = MeshCanvasDirtyFlag.kNone;
            }

            mesh.MarkDynamic();

            if ((m_dirtyFlag & MeshCanvasDirtyFlag.kVisible) == MeshCanvasDirtyFlag.kVisible)
            {
                mesh.Clear();

                mesh.SetVertices(m_Positions);
                mesh.SetUVs(0, m_Uv0S);
                if (m_offsetInfo.useColor)
                {
                    mesh.SetColors(m_Colors);
                }

                mesh.SetTriangles(m_Indices, 0);
                mesh.RecalculateBounds();
                return;
            }

            if ((m_dirtyFlag & MeshCanvasDirtyFlag.kPosition) == MeshCanvasDirtyFlag.kPosition)
            {
                mesh.SetVertices(m_Positions);
            }

            if ((m_dirtyFlag & MeshCanvasDirtyFlag.kColor) == MeshCanvasDirtyFlag.kColor)
            {
                mesh.SetColors(m_Colors);
            }

            if ((m_dirtyFlag & MeshCanvasDirtyFlag.kUv) == MeshCanvasDirtyFlag.kUv)
            {
                mesh.SetUVs(0, m_Uv0S);
            }

            m_dirtyFlag = MeshCanvasDirtyFlag.kNone;
        }
    }
}